﻿using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using CloudShot.Utils;
using CloudShot.Utils.Drawings;

namespace CloudShot.Forms
{
  public partial class RecordBackgroundForm : MoveableForm
  {
    private readonly Font _cooldownFont = new Font("Segoe UI", 15);
    private static Bitmap _backgroundBitmap;
    private string _label;

    public string Label
    {
      get { return _label; }
      set
      {
        _label = value;
        Redraw();
      }
    }

    protected override CreateParams CreateParams
    {
      get
      {
        // Add the layered extended style (WS_EX_LAYERED) to this window.
        CreateParams createParams = base.CreateParams;
        createParams.ExStyle |= WS_EX_LAYERED | WS_EX_TOOLWINDOW;
        return createParams;
      }
    }

    protected override void OnResize(EventArgs e)
    {
      base.OnResize(e);
      Redraw();
    }

    public RecordBackgroundForm()
    {
      InitializeComponent();
    }

    private void Redraw()
    {
      if (Width <= 0 || Height <= 0)
        return;

      if (_backgroundBitmap == null || _backgroundBitmap.Width < Width || _backgroundBitmap.Height < Height)
      {
        if (_backgroundBitmap != null)
        {
          _backgroundBitmap.Dispose();
        }

        int width = Math.Max(1024, CalculateUpperPowerOfTwo(Width));
        int height = Math.Max(1024, CalculateUpperPowerOfTwo(Height));
        _backgroundBitmap = new Bitmap(width, height);
      }

      DrawBackground();
      ApplyBitmap();
    }

    private void DrawBackground()
    {
      if (_backgroundBitmap == null)
        return;

      int horizontalPadding = (int)(ClientRectangle.Width * 0.10);
      int verticalPadding = (int)(ClientRectangle.Height * 0.10);
      Rectangle labelBounds = ClientRectangle.ApplyPadding(new Padding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding));

      SizeF size;

      using (var graphics = Graphics.FromImage(_backgroundBitmap))
      using (var fillBrush = new SolidBrush(Color.FromArgb(150, 100, 100, 100)))
      using (var font = graphics.AppropriateFont(15f, 34f, labelBounds.Size, Label, _cooldownFont, out size))
      {
        var format = new StringFormat(StringFormat.GenericTypographic)
        {
          Alignment = StringAlignment.Center,
          LineAlignment = StringAlignment.Center
        };

        graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
        graphics.SmoothingMode = SmoothingMode.AntiAlias;
        graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;

        graphics.Clear(Color.Transparent);
        graphics.FillRectangle(fillBrush, 0, 0, Width, Height);
        graphics.DrawString(Label, font, Brushes.White, labelBounds, format);
        //graphics.DrawRectangle(Pens.Red, rect);
      }
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="opacity">
    /// Specifies an alpha transparency value to be used on the entire source 
    /// bitmap. The SourceConstantAlpha value is combined with any per-pixel 
    /// alpha values in the source bitmap. The value ranges from 0 to 255. If 
    /// you set SourceConstantAlpha to 0, it is assumed that your image is 
    /// transparent. When you only want to use per-pixel alpha values, set 
    /// the SourceConstantAlpha value to 255 (opaque).
    /// </param>
    private void ApplyBitmap(int opacity = 255)
    {
      // Does this bitmap contain an alpha channel?
      if (_backgroundBitmap.PixelFormat != PixelFormat.Format32bppArgb)
      {
        throw new ApplicationException("The bitmap must be 32bpp with alpha-channel.");
      }

      // Get device contexts
      IntPtr screenDc = GetDC(IntPtr.Zero);
      IntPtr memDc = CreateCompatibleDC(screenDc);
      IntPtr hBitmap = IntPtr.Zero;
      IntPtr hOldBitmap = IntPtr.Zero;

      try
      {
        // Get handle to the new bitmap and select it into the current 
        // device context.
        hBitmap = _backgroundBitmap.GetHbitmap(Color.FromArgb(0));
        hOldBitmap = SelectObject(memDc, hBitmap);

        // Set parameters for layered window update.
        //Size newSize = new Size(bitmap.Width, bitmap.Height);
        Win32Size newSize = new Win32Size(Width, Height);
        Point sourceLocation = new Point(0, 0);
        Point newLocation = new Point(this.Left, this.Top);
        BLENDFUNCTION blend = new BLENDFUNCTION();
        blend.BlendOp = AC_SRC_OVER;
        blend.BlendFlags = 0;
        blend.SourceConstantAlpha = (byte)opacity;
        blend.AlphaFormat = AC_SRC_ALPHA;

        // Update the window.
        UpdateLayeredWindow(Handle, // Handle to the layered window
                            screenDc, // Handle to the screen DC
                            ref newLocation, // New screen position of the layered window
                            ref newSize, // New size of the layered window
                            memDc, // Handle to the layered window surface DC
                            ref sourceLocation, // Location of the layer in the DC
                            0, // Color key of the layered window
                            ref blend, // Transparency of the layered window
                            ULW_ALPHA // Use blend as the blend function
          );
      }
      finally
      {
        // Release device context.
        ReleaseDC(IntPtr.Zero, screenDc);
        if (hBitmap != IntPtr.Zero)
        {
          SelectObject(memDc, hOldBitmap);
          DeleteObject(hBitmap);
        }
        DeleteDC(memDc);
      }
    }

    protected override void OnLoad(EventArgs e)
    {
      DragControl = this;
      base.OnLoad(e);
    }

    #region Helpers

    private int CalculateUpperPowerOfTwo(int v)
    {
      //http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
      //http://stackoverflow.com/questions/13738325/opengl-making-many-textures-power-of-2
      v--;
      v |= v >> 1;
      v |= v >> 2;
      v |= v >> 4;
      v |= v >> 8;
      v |= v >> 16;
      v++;
      return v;

    }

    private bool IsPowerOfTwo(int i)
    {
      return (i & (i - 1)) == 0;
    }

    #endregion

    #region Native Methods and Structures

    private const Int32 WS_EX_LAYERED = 0x80000;
    private const int WS_EX_TOOLWINDOW = 0x00000080;
    private const Int32 HTCAPTION = 0x02;
    private const Int32 WM_NCHITTEST = 0x84;
    private const Int32 ULW_ALPHA = 0x02;
    private const byte AC_SRC_OVER = 0x00;
    private const byte AC_SRC_ALPHA = 0x01;

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref Point pptDst, ref Win32Size psize, IntPtr hdcSrc, ref Point pprSrc, Int32 crKey,
                                                   ref BLENDFUNCTION pblend, Int32 dwFlags);

    [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr CreateCompatibleDC(IntPtr hDC);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr GetDC(IntPtr hWnd);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);

    [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool DeleteDC(IntPtr hdc);

    [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);

    [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool DeleteObject(IntPtr hObject);

    #region Nested type: ARGB

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    private struct ARGB
    {
      public readonly byte Blue;
      public readonly byte Green;
      public readonly byte Red;
      public readonly byte Alpha;
    }

    #endregion

    #region Nested type: BLENDFUNCTION

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    private struct BLENDFUNCTION
    {
      public byte BlendOp;
      public byte BlendFlags;
      public byte SourceConstantAlpha;
      public byte AlphaFormat;
    }

    #endregion

    #region Nested type: Point

    [StructLayout(LayoutKind.Sequential)]
    private struct Point
    {
      public readonly Int32 x;
      public readonly Int32 y;

      public Point(Int32 x, Int32 y)
      {
        this.x = x;
        this.y = y;
      }
    }

    #endregion

    #region Nested type: Size

    [StructLayout(LayoutKind.Sequential)]
    private struct Win32Size
    {
      public readonly Int32 cx;
      public readonly Int32 cy;

      public Win32Size(Int32 cx, Int32 cy)
      {
        this.cx = cx;
        this.cy = cy;
      }
    }

    #endregion

    #endregion
  }
}